#pragma once

#include "index.hpp"
#include "util.hpp"
#include <algorithm>
#include "jsoncpp/json/json.h"
#include "log.hpp"

namespace ns_search
{
    /* 用于解决搜索内容分词后多个搜索词最终可能对应相同HTML文件而进行去重的结构体 */
    struct inverted_words 
    {
        uint64_t doc_id;
        vector<string> keywords; // 一个HTML文件对应多个搜索词
        int weight; 
        inverted_words():doc_id(0),weight(0){}
    };

    class search
    {
        private:
            ns_index::index* _index;
        
        public:
            search()
            {

            }

            ~search()
            {

            }

            void InitSearch(const string& input_file)
            {
                /* 创建或获取index类型对象，通过对象建立索引 */
                _index = ns_index::index::GetInstance();
                _index->BuildIndex(input_file);
            }

            void Search(const string& query,string* json_str)
            {
                /*
                    1. 通过jieba对query分词
                    2. 将搜索关键词进行字母小写化处理，将多个搜索关键词对应的倒排拉链合并去重
                       后存入vector<inverted_words>类型变量中，即通过doc_id标识搜索关键词对应结果
                    3. 将inverted_words_total中元素根据weight排序（降序）
                    4. doc_id找到正排索引，对正排索引中title、content、url构建json串
                       content只取部分内容组成Description
                */

                /* 1. 分词 */
                vector<string> query_keywords;
                ns_util::jieba_util::CutString(query,&query_keywords);
 
                /* 2. 合并多个搜索词对应倒排拉链，并对倒排拉链去重 */
                vector<inverted_words> inverted_words_total; // 存放去重后的结果
                unordered_map<uint64_t,inverted_words> unique_map; // 用于去重的unordered_map容器

                for(string keyword : query_keywords)
                {
                    boost::to_lower(keyword); // 对搜索关键词进行字母小写化处理
                    ns_index::inverted_list* list = _index->GetInvertedList(keyword); // 获取搜索关键词对应倒排拉链
                    if(nullptr == list)
                    {
                        string log_message("keyword：");
                        log_message += keyword;
                        log_message += " not find";
                        LOG(WARNING,log_message); 

                        //std::cout << keyword << "not find" << std::endl;
                        
                        continue;
                    }
                    /* 通过doc_id标识搜索关键词对应结果，避免多个搜索关键词在一个文档中出现，造成最终结果中有多个相同HTML文件 */
                    for(const auto& elem : *list)
                    {
                        auto& item_elem = unique_map[elem.doc_id];
                        item_elem.doc_id = elem.doc_id;
                        item_elem.keywords.push_back(elem.keyword);
                        item_elem.weight += elem.weight;
                    }
                }  

                for(const auto& elem : unique_map)
                {
                    inverted_words_total.push_back(std::move(elem.second));
                }

                /* 3. 根据weight对结果排序 */
                std::sort(inverted_words_total.begin(), inverted_words_total.end(),\
                     [](const inverted_words& e1, const inverted_words& e2){
                       return e1.weight > e2.weight;
                       });

                /* 
                      4. 通过doc_id 找到正排索引构建json串
                      json串中存放：title、description、url
                 */
                Json::Value root;
                for(auto& elem : inverted_words_total)
                {
                    ns_index::doc_info* doc = _index->GetForwardIndex(elem.doc_id);
                    if(nullptr == doc)
                    {
                        continue;
                    }

                    Json::Value item; 
                    item["title"] = doc->title;
                    item["desc"] = GetDescription(doc->content,elem.keywords[0]);
                    item["url"] = doc->url;

                    root.append(item);
                }

                Json::FastWriter writer; // json中多个元素不换行，提高传输效率
                *json_str =  writer.write(root);
            }

            string GetDescription(const string& content,const string& keyword)
            {
                /*
                    从content查找keyword第一次出现位置，并返回该位置前pos_prev字节到后pos_next字节的内容
                    该位置前没有pos_prev字节时beg = 0,该位置后没有pos_next字节时end = content.size() - 1

                    注意：由于搜索词传入时是小写，但是数据清洗后的HTML元素中content存放内容并没有转换为小写(可能存在大写)
                         需要在查找时按小写查找

                    结果：
                        1. content中没有找到keyword, return "Debug1" ,这种情况不可能出现，除非建立索引部分代码有Bug
                        2. beg >= end,return "Debug2" 这种情况几乎不可能出现，除非搜索词为一个字符，且HTML网页内容只有这一个字符
                        3. 匹配到对应内容， return content.substr(beg,end-beg);
                */
                auto iterator = std::search(content.begin(),content.end(), keyword.begin(), keyword.end(), [](int x, int y){
                        return (std::tolower(x) == std::tolower(y));
                        });

                if(iterator == content.end())
                {
                    LOG(DEBUG ,"Debug1"); 

                    return "Debug1";
                }    

                size_t pos = std::distance(content.begin(), iterator); // 返回iterator在字符串中下标　

                const size_t pos_prev = 50;
                const size_t pos_next = 50;
                size_t beg = 0;
                size_t end = content.size() - 1;
                
                if(beg + pos_prev < pos)
                {
                    beg = pos - pos_prev;
                }

                if(pos + pos_next < end)
                {
                    end = pos + pos_next; 
                }

                if(beg >= end)
                {
                    LOG(DEBUG ,"Debug2"); 

                    return "Debug2";
                }

                string desc = content.substr(beg,end-beg);
                desc += "......";
            
                return desc;
            }

    };
}

